using System;
using System.IO;
using System.Text;
using ExcelDataReader;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using HuaXianQu.ProtoBuffEx.Runtime;
using UnityEditor;

namespace HuaXianQu.ProtoBuffEx.ToolEditor
{
    public class ExcelToCsHarp
    {
        [Serializable]
        class AssemblyDefinitionEditorImporter
        {
            public string name;
            public string rootNamespace;
            public List<string> references;
            public List<string> includePlatforms;
            public List<string> excludePlatforms;
            public bool allowUnsafeCode;
            public bool overrideReferences;
            public List<string> precompiledReferences;
            public bool autoReferenced;
            public List<string> defineConstraints;
            public List<string> versionDefines;
            public bool noEngineReferences;
        }

        private static string successStrEnglish =
            @$"<color=green><size=22><b>Excel data conversion to C# data successful. </b></size></color>

Generate DLL or script to:
<i>{Path.GetFullPath(
    ProtoPathConfig.Inst.IsUsedDLL ?
        ProtoPathConfig.Inst.GenerateProtoDllPath :
        ProtoPathConfig.Inst.GenerateCsCachePath)}</i>.

Generated data file path:
<i>{Path.GetFullPath(ProtoPathConfig.Inst.GenerateProtoDataPath)}</i>.
";

        private static string successStrChinese =
            @$"<color=green><size=22><b>Excel转换成功。</b></size></color>

生成dll或者脚本到:
<i>{Path.GetFullPath(
    ProtoPathConfig.Inst.IsUsedDLL ?
        ProtoPathConfig.Inst.GenerateProtoDllPath :
        ProtoPathConfig.Inst.GenerateCsCachePath)}</i>。

生成数据文件路径：
<i>{Path.GetFullPath(ProtoPathConfig.Inst.GenerateProtoDataPath)}。</i>";

        private readonly string[] _filter = { "_ignore" };
        string DirectoryRoot => ProtoPathConfig.Inst.GenerateProtoPath;
        string ProtoBuffFilePath => $"{DirectoryRoot}/{ConfigPath}";
        string ExcelPath => ProtoPathConfig.Inst.ExcelPath;
        private List<IProtoToClassParse> _classGens;
        private string ExcelVariableAtttibutePath => "ExcelVariableAttribute.proto";
        private string ConfigPath => "Config";
        private string ClientPath => "Client";


        public class CSStruct
        {
            public string Body;
            public string BodyDcit;
            public string BodyIf;
            public string FunctionName;
            public string FunctionBody;
            public string ParseDictionary;
            public string GetDictionary;
            public string GetDictionaryReturn;
            public string GetMap;
        }

        private CSStruct _curCsStruct;

        private CSStruct CurCsStruct
        {
            get
            {
                if (_curCsStruct == null)
                {
                    string[] csString = File.ReadAllLines(
                        $"{ProtoPathConfig.Inst.PackagesPath}/Editor/Editor Resouces/ProtoParseTmep.txt");
                    _curCsStruct = new CSStruct();
                    List<string> csList = new();
                    for (int i = 0; i < csString.Length; i++)
                    {
                        string cs = csString[i];
                        Match match = Regex.Match(cs, @"[\s\t]*\$\$\$\$([\d\w]+)\$\$\$\$[\s\t\n]*");
                        if (match.Groups.Count == 2)
                        {
                            string variableName = match.Groups[1].Value;
                            FieldInfo fieldInfo = _curCsStruct.GetType().GetField(variableName);
                            if (fieldInfo == null)
                            {
                                ProtoDebug.LogError($"未找到变量{variableName}");
                                break;
                            }

                            fieldInfo.SetValue(_curCsStruct, string.Join("\n", csList));
                            csList.Clear();
                        }
                        else
                        {
                            csList.Add(cs);
                        }
                    }
                }

                return _curCsStruct;
            }
        }

        [MenuItem("Tools/ExcelToCSHarp")]
        static void ReadExcel()
        {
            ExcelToCsHarp excelToCsHarp = new ExcelToCsHarp();
            excelToCsHarp.StartReadExcel();
        }

        public void StartReadExcel()
        {
            _classGens = new List<IProtoToClassParse>();

            if (!Directory.Exists(ExcelPath))
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool",
                    @$"Excel文件夹不存在，路径：{ExcelPath}。如果自定义Excel路径，请重载DefaultIProtoPathConfig。详情请看文档", "OK");

                return;
            }

            ClearProtoDataFile();

            ProtoDebug.Reset();
            if (!ExcelToProto())
            {
                return;
            }

            ClearCache();
            ProtobuffToCSharpTool protobuffToCSharpTool = new ProtobuffToCSharpTool();
            if (!protobuffToCSharpTool.GenProtoToCsFile())
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", @$"proto文件转换c#脚本失败", "OK");
                return;
            }

            AssetDatabase.Refresh();
            ProtoDllTool protoDllTool = new ProtoDllTool();
            protoDllTool.CompileToDll();
            ProtoBuffAssembleEditor.Distory();
            ExcelToCsharpData();
            try
            {
            }
            catch (Exception e)
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", @$"生成数据失败,error:{e}", "OK");
            }

            try
            {
                ExcelToDataFile();
            }
            catch (Exception e)
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", @$"生成数据文件失败,error:{e}", "OK");
            }

            try
            {
                GenParserCs();
            }

            catch (Exception e)
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", @$"生成解析脚本错误,error:{e}", "OK");
            }

            try
            {
                //重新生成解析dll，将解析脚本编译到dll
                protoDllTool.CompileToDll();
            }
            catch (Exception e)
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", @$"重新生成解析脚本错误,error:{e}", "OK");
            }

            AssetDatabase.Refresh();
            if (ProtoDebug.IsLogError())
            {
                ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool",
                    $"<color=red><size=22><b>Failed to generate data.</b></size></color>\nerr:{ProtoDebug.LogErrorMassege}\n\n数据转换失败。err:{ProtoDebug.LogErrorMassege}"
                    , "OK");
                return;
            }

            ProtoPopupWin.DisplayDialog("ProtobuffToCSharpTool", successStrEnglish
                , "OK");

            ProtoDebug.Reset();
        }

        private void ClearCache()
        {
            if (Directory.Exists(ProtoPathConfig.Inst.GenerateProtoCsRootPath))
            {
                Directory.Delete(ProtoPathConfig.Inst.GenerateProtoCsRootPath, true);
            }
        }

        private void ClearProtoDataFile()
        {
            if (!Directory.Exists(ProtoPathConfig.Inst.GenerateProtoDataPath))
            {
                return;
            }

            string[] dataFiles = Directory.GetFiles(ProtoPathConfig.Inst.GenerateProtoDataPath, "*.protodat",
                SearchOption.AllDirectories);

            foreach (var dataFile in dataFiles)
            {
                File.Delete(dataFile);
            }
        }

        private void ExcelToCsharpData()
        {
            foreach (var classGen in _classGens)
            {
                classGen.SetData();
            }
        }

        private void ExcelToDataFile()
        {
            foreach (var classGen in _classGens)
            {
                classGen.SaveData();
            }
        }

        private void GenParserCs()
        {
            foreach (var classGen in _classGens)
            {
                (classGen as IProtoGenParserCS)?.GenPaserCS();
            }
        }

        private DataSet GetDataSet(string filePath)
        {
            DataSet result = null;
            using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            var reader = ExcelReaderFactory.CreateReader(stream, new ExcelReaderConfiguration()
            {
                FallbackEncoding = Encoding.GetEncoding(1252),
                // Password = "password",
                AutodetectSeparators = new char[] { ',', ';', '\t', '|', '#' },
                LeaveOpen = false,
                AnalyzeInitialCsvRows = 0,
            });

            result = reader.AsDataSet(new ExcelDataSetConfiguration()
            {
                UseColumnDataType = true,
                ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
                {
                    EmptyColumnNamePrefix = "Column",
                    UseHeaderRow = false,
                    ReadHeaderRow = (rowReader) => { rowReader.Read(); },
                    FilterRow = (rowReader) => { return true; },
                    FilterColumn = (rowReader, columnIndex) => { return true; },
                },
                FilterSheet = (tableReader, sheetIndex) => !IsFilter(tableReader.Name),
            });

            return result;
        }

        private bool IsFilter(string name)
        {
            foreach (var item in _filter)
            {
                if (name.ToLower().EndsWith(item))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 从excel转换到proto文件
        /// </summary>
        private bool ExcelToProto()
        {
            string[] files = Directory.GetFiles(ExcelPath, "*.xlsx");
            if (files.Length == 0)
            {
                return false;
            }

            if (Directory.Exists(ProtoBuffFilePath))
            {
                Directory.Delete(ProtoBuffFilePath, true);
            }

            Directory.CreateDirectory(ProtoBuffFilePath);
            foreach (var file in files)
            {
                ExcelToProtoBuffFlie(file);
            }

            ExcelToProtoBuffFlie();
            return true;
        }


        private readonly List<ExcelToProtoFile> _genProtoInfos = new List<ExcelToProtoFile>();
        private readonly Dictionary<string, string> _objectInfoDcit = new Dictionary<string, string>();

        private void ExcelToProtoBuffFlie(string filePath)
        {
            var result = GetDataSet(filePath);
            List<IProtoType> tableParses = new List<IProtoType>();
            string protoName = Path.GetFileNameWithoutExtension(filePath);
            // 遍历每个工作表
            for (int sheetIndex = 0; sheetIndex < result.Tables.Count; sheetIndex++)
            {
                var table = result.Tables[sheetIndex];
                if (string.IsNullOrEmpty(table.TableName))
                {
                    continue;
                }

                if (table.TableName.ToLower().EndsWith(ClassGen.ClassFeildName)) //自定义类工作表
                {
                    tableParses.AddRange(CostomExcelToFlieByTable(table, protoName));
                }
                else if (table.TableName.ToLower().EndsWith(ProtoEnumGen.EnumFeildName)) //枚举类型工作表
                {
                    tableParses.AddRange(ExcelToEnumByTable(table, protoName));
                }
                else if (table.TableName.ToLower().EndsWith(SingleClassGen.ClassFeildName)) //单个类数据工作表
                {
                    IProtoToClassParse singleClassGen = ExcelToSingleClass(table, protoName);
                    tableParses.Add(singleClassGen);
                    _classGens.Add(singleClassGen);
                }
                else
                {
                    IProtoType protoType = ExcelToCommon(table, protoName); //列表类数据定义
                    if (protoType != null)
                    {
                        tableParses.Add(protoType);
                    }
                }
            }

            ExcelToProtoFile excelToProtoFile = new ExcelToProtoFile();
            excelToProtoFile.Init(protoName, ConfigPath, DirectoryRoot, tableParses);
            foreach (var className in excelToProtoFile.CustomClassList)
            {
                if (_objectInfoDcit.ContainsKey(className))
                {
                    ProtoDebug.LogError($"ProtoToCSharpTool:类{protoName}重复定义:{className}");
                    continue;
                }

                _objectInfoDcit.Add(className, excelToProtoFile.ProtoBuffFilePath);
            }

            _genProtoInfos.Add(excelToProtoFile);
        }

        private void ExcelToProtoBuffFlie()
        {
            string arrtibuteConfigName = $@"import ""{ClientPath}/{ExcelVariableAtttibutePath}"";";
            HashSet<string> importPathList = new HashSet<string>();
            foreach (var info in _genProtoInfos)
            {
                importPathList.Clear();
                for (int i = 0; i < info.ImportList.Count; i++)
                {
                    if (_objectInfoDcit.TryGetValue(info.ImportList[i], out string className))
                    {
                        importPathList.Add(className);
                    }
                    else
                    {
                        ProtoDebug.LogError($"ProtoToCSharpTool:类{info.ProtoName}没有找到自定义类或者枚举:{info.ImportList[i]}");
                        throw new Exception($"ProtoToCSharpTool:类{info.ProtoName}没有找到自定义类或者枚举:{info.ImportList[i]}");
                    }
                }

                info.ImportList = importPathList.ToList();

                info.ExcelToProtoBuffFlie(arrtibuteConfigName, info.ImportList);
            }
        }

        /// <summary>
        /// 单个类数据工作表
        /// </summary>
        /// <param name="table"></param>
        /// <param name="protoName"></param>
        /// <returns></returns>
        private IProtoToClassParse ExcelToSingleClass(DataTable table, string protoName)
        {
            SingleClassGen publicClassGen = new SingleClassGen();
            publicClassGen.Parse(table);
            publicClassGen.CurObjectInfo.ProtoFileName = protoName;
            return publicClassGen;
        }

        /// <summary>
        /// 枚举类型工作表
        /// </summary>
        /// <param name="table"></param>
        /// <param name="protoName"></param>
        /// <returns></returns>
        private List<ProtoEnumGen> ExcelToEnumByTable(DataTable table, string protoName)
        {
            List<ProtoEnumGen> publicClassGenList = new List<ProtoEnumGen>();
            for (int i = 0; i < table.Rows.Count; i++)
            {
                List<string> publicClassGenStringList = new List<string>();
                ProtoEnumGen publicClassGen = new ProtoEnumGen();
                var row = table.Rows[i];
                for (int j = 0; j < table.Columns.Count; j++)
                {
                    if (string.IsNullOrEmpty(row[j].ToString()))
                    {
                        continue;
                    }

                    publicClassGenStringList.Add(row[j].ToString());
                }

                if (publicClassGenStringList.Count == 0)
                {
                    continue;
                }

                publicClassGen.Parse(publicClassGenStringList);
                publicClassGen.CurObjectInfo.ProtoFileName = protoName;
                publicClassGenList.Add(publicClassGen);
            }

            return publicClassGenList;
        }

        /// <summary>
        /// 自定义类工作表
        /// </summary>
        /// <param name="table"></param>
        /// <param name="protoName"></param>
        /// <returns></returns>
        private List<CustomClassGen> CostomExcelToFlieByTable(DataTable table, string protoName)
        {
            List<CustomClassGen> publicClassGenList = new List<CustomClassGen>();
            for (int i = 0; i < table.Rows.Count; i++)
            {
                List<string> publicClassGenStringList = new List<string>();
                CustomClassGen customClassGen = new CustomClassGen();
                var row = table.Rows[i];
                for (int j = 0; j < table.Columns.Count; j++)
                {
                    if (string.IsNullOrEmpty(row[j].ToString()))
                    {
                        continue;
                    }

                    publicClassGenStringList.Add(row[j].ToString());
                }

                if (publicClassGenStringList.Count == 0)
                {
                    continue;
                }

                customClassGen.Parse(publicClassGenStringList);
                customClassGen.CurObjectInfo.ProtoFileName = protoName;
                publicClassGenList.Add(customClassGen);
            }

            return publicClassGenList;
        }

        /// <summary>
        /// 列表类数据定义
        /// </summary>
        /// <param name="table"></param>
        /// <param name="protoName"></param>
        /// <returns></returns>
        private ClassListGen ExcelToCommon(DataTable table, string protoName)
        {
            try
            {
                ClassListGen classListGen = new ClassListGen(CurCsStruct);
                classListGen.Parse(table);
                _classGens.Add(classListGen);
                classListGen.CurObjectInfo.ProtoFileName = protoName;
                return classListGen;
            }
            catch (Exception e)
            {
                ProtoDebug.LogError($"解析错误:{table.TableName},err:{e}");
            }

            return null;
        }
    }
}